rendernodeparser: Parse cairo script
authorBenjamin Otte <otte@redhat.com>
Tue, 28 May 2019 03:51:20 +0000 (05:51 +0200)
committerBenjamin Otte <otte@redhat.com>
Thu, 30 May 2019 13:32:36 +0000 (15:32 +0200)
Use cairo-script-interpreter to parse the scripts that generate cairo
nodes.

This requires libcairoscriptinterpreter.so to work properly, but if
it isn't found we disable this (unimportant for normal functioning)
code and just emits a parser warning.
The testsuite requires it however or it will fail.

A new test is included that tests all of this.

config.h.meson
gsk/gskrendernodeparser.c
gsk/meson.build
gtk/css/gtkcssenums.h
meson.build
testsuite/gsk/compare/scaled-cairo.node [new file with mode: 0644]
testsuite/gsk/compare/scaled-cairo.png [new file with mode: 0644]
testsuite/gsk/meson.build

index e52badaede5e900c4e475808daa1227430101fb8..a8ea83d0186b3d7d932588a64bbb4933888acab2 100644 (file)
 
 #mesondefine GTK_PRINT_BACKENDS
 
+#mesondefine HAVE_CAIRO_SCRIPT_INTERPRETER
+
 #mesondefine HAVE_HARFBUZZ
 
 #mesondefine HAVE_PANGOFT
index 59b8a4292cb4ff000bbe7e53624c570c122b2ff1..6dc1d19448e56a004551868a2138e58720472742 100644 (file)
@@ -36,6 +36,9 @@
 #ifdef CAIRO_HAS_SCRIPT_SURFACE
 #include <cairo-script.h>
 #endif
+#ifdef HAVE_CAIRO_SCRIPT_INTERPRETER
+#include <cairo-script-interpreter.h>
+#endif
 
 typedef struct _Declaration Declaration;
 
@@ -132,6 +135,131 @@ clear_texture (gpointer inout_texture)
   g_clear_object ((GdkTexture **) inout_texture);
 }
 
+static cairo_surface_t *
+csi_hooks_surface_create (void            *closure,
+                          cairo_content_t  content,
+                          double           width,
+                          double           height,
+                          long             uid)
+{
+  return cairo_surface_create_similar (closure, content, width, height);
+}
+
+static const cairo_user_data_key_t csi_hooks_key;
+
+static cairo_t *
+csi_hooks_context_create (void            *closure,
+                          cairo_surface_t *surface)
+{
+  cairo_t *cr = cairo_create (surface);
+
+  cairo_set_user_data (cr,
+                       &csi_hooks_key,
+                       cairo_surface_reference (surface),
+                       (cairo_destroy_func_t) cairo_surface_destroy);
+
+  return cr;
+}
+
+static void
+csi_hooks_context_destroy (void *closure,
+                           void *ptr)
+{
+  cairo_surface_t *surface;
+  cairo_t *cr;
+
+  surface = cairo_get_user_data (ptr, &csi_hooks_key);
+  cr = cairo_create (closure);
+  cairo_set_source_surface (cr, surface, 0, 0);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+}
+
+static gboolean
+parse_script (GtkCssParser *parser,
+              gpointer      out_data)
+{
+#ifdef HAVE_CAIRO_SCRIPT_INTERPRETER
+  GError *error = NULL;
+  GBytes *bytes;
+  GtkCssLocation start_location;
+  char *url, *scheme;
+  cairo_script_interpreter_t *csi;
+  cairo_script_interpreter_hooks_t hooks = {
+    .surface_create = csi_hooks_surface_create,
+    .context_create = csi_hooks_context_create,
+    .context_destroy = csi_hooks_context_destroy,
+  };
+
+  start_location = *gtk_css_parser_get_start_location (parser);
+  url = gtk_css_parser_consume_url (parser);
+  if (url == NULL)
+    return FALSE;
+
+  scheme = g_uri_parse_scheme (url);
+  if (scheme && g_ascii_strcasecmp (scheme, "data") == 0)
+    {
+      bytes = gtk_css_data_url_parse (url, NULL, &error);
+    }
+  else
+    {
+      GFile *file;
+
+      file = gtk_css_parser_resolve_url (parser, url);
+      bytes = g_file_load_bytes (file, NULL, NULL, &error);
+      g_object_unref (file);
+    }
+
+  g_free (scheme);
+  g_free (url);
+
+  if (bytes == NULL)
+    {
+      gtk_css_parser_emit_error (parser,
+                                 &start_location,
+                                 gtk_css_parser_get_end_location (parser),
+                                 error);
+      g_clear_error (&error);
+      return FALSE;
+    }
+
+  hooks.closure = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
+  csi = cairo_script_interpreter_create ();
+  cairo_script_interpreter_install_hooks (csi, &hooks);
+  cairo_script_interpreter_feed_string (csi, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
+  g_bytes_unref (bytes);
+  if (cairo_surface_status (hooks.closure) != CAIRO_STATUS_SUCCESS)
+    {
+      gtk_css_parser_error_value (parser, "Invalid Cairo script: %s", cairo_status_to_string (cairo_surface_status (hooks.closure)));
+      cairo_script_interpreter_destroy (csi);
+      return FALSE;
+    }
+  if (cairo_script_interpreter_destroy (csi) != CAIRO_STATUS_SUCCESS)
+    {
+      gtk_css_parser_error_value (parser, "Invalid Cairo script");
+      cairo_surface_destroy (hooks.closure);
+      return FALSE;
+    }
+
+  *(cairo_surface_t **) out_data = hooks.closure;
+  return TRUE;
+#else
+  gtk_css_parser_warn (parser,
+                       GTK_CSS_PARSER_WARNING_UNIMPLEMENTED,
+                       gtk_css_parser_get_block_location (parser),
+                       gtk_css_parser_get_start_location (parser),
+                       "GTK was compiled with script interpreter support. Using fallback pixel data for Cairo node.");
+  *(cairo_surface_t **) out_data = NULL;
+  return TRUE;
+#endif
+}
+
+static void
+clear_surface (gpointer inout_surface)
+{
+  g_clear_pointer ((cairo_surface_t **) inout_surface, cairo_surface_destroy);
+}
+
 static gboolean
 parse_rounded_rect (GtkCssParser *parser,
                     gpointer      out_rect)
@@ -930,9 +1058,11 @@ parse_cairo_node (GtkCssParser *parser)
 {
   graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
   GdkTexture *pixels = NULL;
+  cairo_surface_t *surface = NULL;
   const Declaration declarations[] = {
     { "bounds", parse_rect, NULL, &bounds },
-    { "pixels", parse_texture, clear_texture, &pixels }
+    { "pixels", parse_texture, clear_texture, &pixels },
+    { "script", parse_script, clear_surface, &surface }
   };
   GskRenderNode *node;
   cairo_t *cr;
@@ -943,13 +1073,16 @@ parse_cairo_node (GtkCssParser *parser)
   
   cr = gsk_cairo_node_get_draw_context (node);
 
-  if (pixels != NULL)
+  if (surface != NULL)
+    {
+      cairo_set_source_surface (cr, surface, 0, 0);
+      cairo_paint (cr);
+    }
+  else if (pixels != NULL)
     {
-      cairo_surface_t *surface;
       surface = gdk_texture_download_surface (pixels);
       cairo_set_source_surface (cr, surface, 0, 0);
       cairo_paint (cr);
-      cairo_surface_destroy (surface);
     }
   else
     {
@@ -959,6 +1092,7 @@ parse_cairo_node (GtkCssParser *parser)
 
   cairo_destroy (cr);
   g_clear_object (&pixels);
+  g_clear_pointer (&surface, cairo_surface_destroy);
 
   return node;
 }
index 5b3e4d7ee6643c2056562ff24d5d7b06e39db8a2..55064806b6ae5be1b76e5c85dc2728905d18e60d 100644 (file)
@@ -156,6 +156,7 @@ gsk_deps = [
   graphene_dep,
   pango_dep,
   cairo_dep,
+  cairo_csi_dep,
   pixbuf_dep,
   libgdk_dep,
 ]
index 1894b55b7485d14d1ff67e134b3b3ab3bc854b84..5470a61e51cea340929a6b988e1fb922c9498bce 100644 (file)
@@ -62,6 +62,8 @@ typedef enum
  *     deprecated and will be removed in a future version
  * @GTK_CSS_PARSER_WARNING_SYNTAX: A syntax construct was used
  *     that should be avoided
+ * @GTK_CSS_PARSER_WARNING_UNIMPLEMENTED: A feature is not
+ *     implemented
  *
  * Warnings that can occur while parsing CSS.
  *
index 34cf3cfcc55b9349520f504283c6a177ea6c4810..3ea4ac57d2f15eb2be972b91bf51b28d80f8a0e5 100644 (file)
@@ -461,11 +461,14 @@ if cc.get_id() == 'msvc'
   endif
 endif
 
+cairo_csi_dep = cc.find_library('cairo-script-interpreter')
+
 if not harfbuzz_dep.found()
   harfbuzz_dep = dependency('harfbuzz', version: '>= 0.9', required: false,
                             fallback: ['harfbuzz', 'libharfbuzz_dep'])
 endif
 
+cdata.set('HAVE_CAIRO_SCRIPT_INTERPRETER', cairo_csi_dep.found())
 cdata.set('HAVE_HARFBUZZ', harfbuzz_dep.found())
 cdata.set('HAVE_PANGOFT', pangoft_dep.found())
 
diff --git a/testsuite/gsk/compare/scaled-cairo.node b/testsuite/gsk/compare/scaled-cairo.node
new file mode 100644 (file)
index 0000000..7606c40
--- /dev/null
@@ -0,0 +1,7 @@
+transform {
+  transform: scale(0.5);
+  child: cairo {
+    bounds: 0 0 100 100;
+    script: url("data:;base64,JSFDYWlyb1NjcmlwdAo8PCAvY29udGVudCAvL0NPTE9SX0FMUEhBIC93aWR0aCA1MCAvaGVpZ2h0IDUwID4+IHN1cmZhY2UgY29udGV4dAoxIDAgMC44IHJnYiBzZXQtc291cmNlCnBhaW50CnBvcAo=");
+  }
+}
diff --git a/testsuite/gsk/compare/scaled-cairo.png b/testsuite/gsk/compare/scaled-cairo.png
new file mode 100644 (file)
index 0000000..c900788
Binary files /dev/null and b/testsuite/gsk/compare/scaled-cairo.png differ
index 4f469603a98231e3cb06bd60e847fb9dec624873..f2bc21b920ceceec18e756167d7f53264d13fdd5 100644 (file)
@@ -23,6 +23,7 @@ compare_render_tests = [
   'clip-coordinates-3d',
   'clipped_rounded_clip',
   'color-blur0',
+  'color-matrix-identity',
   'cross-fade-in-opacity',
   'empty-blend',
   'empty-blur',
@@ -50,9 +51,9 @@ compare_render_tests = [
   'outset_shadow_offset_y',
   'outset_shadow_rounded_top',
   'outset_shadow_simple',
+  'scaled-cairo',
   'shadow-in-opacity',
   'texture-url',
-  'color-matrix-identity',
 ]
 
 renderers = [